package com.cfushn.ddd.tools.crypto;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;

/**
 * RSAUtil
 * <p>
 * rsa create distribute en/decrypt destroy
 *
 * @author CFuShn 2022/11/22 11:08
 */
@Component
@SuppressWarnings("unused")
public class RsaUtil {

    /** 当前系统默认rsa密钥对 */
    public static final KeyPair KEY_PAIR = generateKeyPair();

    /** 签名算法名称 */
    public static final String SIGN_ALGORITHMS = "SHA256withRSA/PSS";

    /** 加密算法名称 */
    public static final String KEY_ALGORITHM_PADDING = "RSA/ECB/PKCS1Padding";

    /** 加密算法名称 OAEP */
    public static final String KEY_ALGORITHM_OAEP_PADDING = "RSA/None/OAEPPadding";

    /** 公钥长度 */
    public static final int KEY_LEN = 2048;

    static {
        // 全局设置加密provider 像SHA256withRSA/PSS 和 RSA/None/OAEPPadding官方不支持,需要第三方支持
        Provider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }

    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            SecureRandom random = new SecureRandom();
            generator.initialize(KEY_LEN, random);
            return generator.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static RSAPublicKey getRsaPublicKeyFromBase64(String base64s)
        throws NoSuchAlgorithmException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64s));
        RSAPublicKey publicKey = null;
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        try {
            publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (InvalidKeySpecException e) {
            // 不报错
            System.out.println(e + ", base64编码=" + base64s + "转RSA公钥失败");
        }

        return publicKey;
    }

    public static RSAPrivateKey getRsaPrivateKeyFromBase64(String base64s)
        throws NoSuchAlgorithmException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64s));
        RSAPrivateKey privateKey = null;
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        try {
            privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (InvalidKeySpecException e) {
            // 不报错
            System.out.println(e + ", base64编码=" + base64s + "转RSA私钥失败");
        }

        return privateKey;
    }

    public static String getBase64PublicKey(KeyPair keyPair) {
        PublicKey publicKey = keyPair.getPublic();
        return Base64.getEncoder().encodeToString(publicKey.getEncoded());
    }

    public static String encryptBase64(String string) {
        return encryptBase64(string, KEY_PAIR);
    }

    public static String encryptBase64(String string, KeyPair keyPair) {
        return Base64.getEncoder().encodeToString(encrypt(string, keyPair));
    }

    private static byte[] encrypt(String string) {
        return encrypt(string, KEY_PAIR);
    }

    private static byte[] encrypt(String string, KeyPair keyPair) {
        try {
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
            return cipher.doFinal(string.getBytes());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String decryptBase64(String string) {
        return decryptBase64(string, KEY_PAIR);
    }

    public static String decryptBase64(String string, KeyPair keyPair) {
        byte[] temp = Base64.getDecoder().decode(string);
        return new String(decrypt(temp, keyPair));
    }

    private static byte[] decrypt(byte[] byteArray) {
        return decrypt(byteArray, KEY_PAIR);
    }

    private static byte[] decrypt(byte[] byteArray, KeyPair keyPair) {
        byte[] plainText;
        try {
            //java默认"RSA/ECB/PKCS1Padding"
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
            plainText = cipher.doFinal(byteArray);
        } catch (Exception e) {
            throw new RuntimeException(KEY_ALGORITHM_PADDING + "解密失败:\n" + e);
        }
        return plainText;
    }

    public static String encryptBase64Oaep(String string, KeyPair keyPair) {
        return Base64.getEncoder().encodeToString(encryptOaep(string, keyPair));
    }

    private static byte[] encryptOaep(String string, KeyPair keyPair) {
        try {
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_OAEP_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
            return cipher.doFinal(string.getBytes());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String decryptBase64Oaep(String string, KeyPair keyPair) {
        byte[] temp = Base64.getDecoder().decode(string);
        return new String(decryptOaep(temp, keyPair));
    }

    private static byte[] decryptOaep(byte[] byteArray, KeyPair keyPair) {
        byte[] plainText;
        try {
            //java默认"RSA/ECB/PKCS1Padding"
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_OAEP_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
            plainText = cipher.doFinal(byteArray);
        } catch (Exception e) {
            throw new RuntimeException(KEY_ALGORITHM_OAEP_PADDING + "解密失败:\n" + e);
        }
        return plainText;
    }

    public static String makeSign(String string, RSAPrivateKey privateKeyBase64) {
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
            signature.initSign(privateKeyBase64);
            signature.update(string.getBytes());
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException(SIGN_ALGORITHMS + "签名失败:\n" + e);
        }
    }

    public static boolean verificationSign(String sign, String content,
        RSAPublicKey publicKeyBase64) {
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
            signature.initVerify(publicKeyBase64);
            signature.update(content.getBytes());
            return signature.verify(Base64.getDecoder().decode(sign));
        } catch (Exception e) {
            System.out.println(SIGN_ALGORITHMS + "验签失败:\n" + e + "\n");
            // 不报错
            return false;
        }
    }

    public static void main(String[] args) {
        //Data must not be longer than 245 bytes
        String string = "hello world";
        String encrypt = encryptBase64(string, KEY_PAIR);
        String decrypt = decryptBase64(encrypt, KEY_PAIR);
        System.out.println("加密前: " + string);
        System.out.println("加密后: " + encrypt);
        System.out.println("解密后: " + decrypt);
        System.out.println("加密前后是否一致: " + string.equals(decrypt));

        String sign = makeSign(string, (RSAPrivateKey) KEY_PAIR.getPrivate());
        boolean verify = verificationSign(sign, string, (RSAPublicKey) KEY_PAIR.getPublic());
        System.out.println("签名: " + sign);
        System.out.println("验签: " + verify);
    }

}
